package tr.edu.ege.kipler.syntax;

import tr.edu.ege.kipler.syntax.atomicformula.Formula;

/**
 * 
 * @author İnanç Seylan
 *
 */
public class BinaryBooleanExpression extends Formula {
	public enum Operator {
		/**
		 * The boolean and.
		 */
		AND,
		/**
		 * The boolean or.
		 */
		OR
	}

	/**
	 * The boolean operator of this formula.
	 */
	private Operator operator;

	/**
	 * An array of length 2 to store the formulas that this binary operator
	 * connects.
	 */
	private WorldElement[] formulas = new WorldElement[2];

	/**
	 * Default constructor
	 */
	public BinaryBooleanExpression() {

	}

	/**
	 * Constructor to initialize all the fields of this class by the given ones.
	 * 
	 * @param operator
	 *            The binary boolean operator
	 * @param lhs
	 *            Left-hand side of this binary connective
	 * @param rhs
	 *            Right-hand side of this binary connective
	 */
	public BinaryBooleanExpression(Operator operator, WorldElement lhs,
			WorldElement rhs) {
		setOperator(operator);
		setLhs(lhs);
		setRhs(rhs);
	}

	/**
	 * @return the binary boolean operator.
	 */
	public Operator getOperator() {
		return operator;
	}

	/**
	 * Sets the binary boolean operator of this formula.
	 * 
	 * @param operator
	 *            The operator to set.
	 */
	public void setOperator(Operator operator) {
		this.operator = operator;
	}

	/**
	 * @see tr.edu.ege.seagent.alcbi.syntax.Formula#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj != null && (obj.getClass().equals(this.getClass()))) {
			BinaryBooleanExpression other = (BinaryBooleanExpression) obj;
			boolean matchedOpsEqual = this.getLhs().equals(other.getLhs())
					&& this.getRhs().equals(other.getRhs());
			boolean unmatchedOpsEqual = this.getLhs().equals(other.getRhs())
					&& this.getRhs().equals(other.getLhs());
			return this.isNegated() == other.isNegated()
					&& this.getOperator().equals(other.getOperator())
					&& (matchedOpsEqual || unmatchedOpsEqual);
		}
		return false;
	}

	/**
	 * Overrides {@link Formula#setNegated(boolean)} because when negated is
	 * true "and" changes to "or" (or vice versa) and the propositions change
	 * sign.
	 */
	public void setNegated(boolean negated) {
		if (negated == true) {
			if (getOperator().equals(Operator.AND)) {
				setOperator(Operator.OR);
			} else {
				setOperator(Operator.AND);
			}
			getLhs().toggleNegated();
			getRhs().toggleNegated();
		}
	}

	public boolean isNegated() {
		return false;
	}

	public WorldElement toggleNegated() {
		setNegated(true);
		return this;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return 17 * 37 + new Boolean(isNegated()).hashCode()
				+ getOperator().hashCode() + getLhs().hashCode()
				+ getRhs().hashCode();
	}

	public BinaryBooleanExpression copy() {
		BinaryBooleanExpression bf = new BinaryBooleanExpression();
		bf.setOperator(getOperator());
		bf.formulas = new WorldElement[] { getLhs().copy(), getRhs().copy() };
		return bf;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(getOperator().toString().toLowerCase());
		buffer.append("(");
		buffer.append(getLhs());
		buffer.append(", ");
		buffer.append(getRhs());
		buffer.append(")");
		if (isNegated()) {
			buffer.insert(0, "not(");
			buffer.append(")");
		}
		return buffer.toString();
	}

	public WorldElement getLhs() {
		return formulas[0];
	}

	public WorldElement getRhs() {
		return formulas[1];
	}

	public void accept(WorldElementVisitor v) {
		v.visitBinaryElement(this);
	}

	public void setLhs(WorldElement e) {
		formulas[0] = e;
	}

	public void setRhs(WorldElement e) {
		formulas[1] = e;
	}
}
